{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Metaclass Parameters"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "When we use a metaclass we typically have something like this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Metaclass(type):\n",
    "    def __new__(mcls, name, bases, cls_dict):\n",
    "        return super().__new__(mcls, name, bases, cls_dict)\n",
    "    \n",
    "class MyClass(metaclass=Metaclass):\n",
    "    pass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(__main__.Metaclass, __main__.MyClass)"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "type(MyClass), type(MyClass())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "But is there a way to pass *additional* arguments to the metaclass `__new__` method?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Starting in Python 3.6, the answer is yes. The restriction is that they **must** be passed as named arguments (positional args being used for specifying inheritance)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "First let's just try out a simple example to understand the syntax:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Metaclass(type):\n",
    "    def __new__(mcls, name, bases, cls_dict, arg1, arg2, arg3=None):\n",
    "        print(arg1, arg2, arg3)\n",
    "        return super().__new__(mcls, name, bases, cls_dict)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "10 20 30\n"
     ]
    }
   ],
   "source": [
    "class MyClass(metaclass=Metaclass, arg1=10, arg2=20, arg3=30):\n",
    "    pass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "10 20 None\n"
     ]
    }
   ],
   "source": [
    "class MyClass(metaclass=Metaclass, arg1=10, arg2=20):\n",
    "    pass"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see our metaclass `__new__` method received those arguments."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's look at a more practical example of this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "class AutoClassAttrib(type):\n",
    "    def __new__(cls, name, bases, cls_dict, extra_attrs=None):\n",
    "        if extra_attrs:\n",
    "            print('Creating class with some extra attributes: ', extra_attrs)\n",
    "            # here I'm going to things directly into the cls_dict namespace\n",
    "            # but could also create the class first, then add using setattr\n",
    "            for attr_name, attr_value in extra_attrs:\n",
    "                cls_dict[attr_name] = attr_value\n",
    "        return super().__new__(cls, name, bases, cls_dict)\n",
    "                "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Creating class with some extra attributes:  [('account_type', 'Savings'), ('apr', 0.5)]\n"
     ]
    }
   ],
   "source": [
    "class Account(metaclass=AutoClassAttrib, extra_attrs=[('account_type', 'Savings'), ('apr', 0.5)]):\n",
    "    pass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "mappingproxy({'__module__': '__main__',\n",
       "              'account_type': 'Savings',\n",
       "              'apr': 0.5,\n",
       "              '__dict__': <attribute '__dict__' of 'Account' objects>,\n",
       "              '__weakref__': <attribute '__weakref__' of 'Account' objects>,\n",
       "              '__doc__': None})"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "vars(Account)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see the class now has these two extra attributes."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We could also have just done it this way:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "class AutoClassAttrib(type):\n",
    "    def __new__(cls, name, bases, cls_dict, extra_attrs=None):\n",
    "        new_cls = super().__new__(cls, name, bases, cls_dict)\n",
    "        if extra_attrs:\n",
    "            print('Creating class with some extra attributes: ', extra_attrs)\n",
    "            for attr_name, attr_value in extra_attrs:\n",
    "                setattr(new_cls, attr_name, attr_value)\n",
    "        return new_cls\n",
    "                "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Creating class with some extra attributes:  [('account_type', 'Savings'), ('apr', 0.5)]\n"
     ]
    }
   ],
   "source": [
    "class Account(metaclass=AutoClassAttrib, extra_attrs=[('account_type', 'Savings'), ('apr', 0.5)]):\n",
    "    pass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "mappingproxy({'__module__': '__main__',\n",
       "              '__dict__': <attribute '__dict__' of 'Account' objects>,\n",
       "              '__weakref__': <attribute '__weakref__' of 'Account' objects>,\n",
       "              '__doc__': None,\n",
       "              'account_type': 'Savings',\n",
       "              'apr': 0.5})"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "vars(Account)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Of course, we could just use `**kwargs` instead, to make it easier:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "class AutoClassAttrib(type):\n",
    "    def __new__(cls, name, bases, cls_dict, **kwargs):\n",
    "        new_cls = super().__new__(cls, name, bases, cls_dict)\n",
    "        if kwargs:\n",
    "            print('Creating class with some extra attributes: ', kwargs)\n",
    "            for attr_name, attr_value in kwargs.items():\n",
    "                setattr(new_cls, attr_name, attr_value)\n",
    "        return new_cls\n",
    "                "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Creating class with some extra attributes:  {'account_type': 'Savings', 'apr': 0.5}\n"
     ]
    }
   ],
   "source": [
    "class Account(metaclass=AutoClassAttrib, account_type='Savings', apr=0.5):\n",
    "    pass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "mappingproxy({'__module__': '__main__',\n",
       "              '__dict__': <attribute '__dict__' of 'Account' objects>,\n",
       "              '__weakref__': <attribute '__weakref__' of 'Account' objects>,\n",
       "              '__doc__': None,\n",
       "              'account_type': 'Savings',\n",
       "              'apr': 0.5})"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "vars(Account)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
